// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
// @output_file_name whi_lib.js
// ==/ClosureCompiler==

/************************************************************************
 *                      Module - whi.js                                 *
 *                                                                      *
 *  Javascript file for WHI turbine control web page.                   *
 *                                                                      *
 *                      ********************                            *
 *                      * REVISION HISTORY *                            *
 *                      ********************                            *
 *                                                                      *
 *         Date         Author          Revisions                       *
 *      ===========     ======          =========                       *
 *      19-Dec-2012     J. Ono          * Modified Start/Stop action    *
 *                                        per PLC firmware v1.30.       *
 *                                                                      *
 *      17-Dec-2012     J. Ono          * Added comments.               *
 *                                                                      *
 *      10-Dec-2012     J. Ono          * Creation.                     *
 ************************************************************************/

/********************************************************
 *      PLC()                                           *
 *                                                      *
 *  Define function just for pseudo-enumerated types.   *
 ********************************************************/
function PLC()
{
  throw Error.notImplemented();
}	// End function PLC

PLC.Input = { vibe_intlk	: 0,
	      overspeed_intlk	: 1,
	      anemometer_pulse	: 2,
	      rpm_pulse		: 3,
	      gen_prot_intlk	: 4,
	      e_stop_intlk	: 5,
	      soft_start_intlk	: 6,
	      SEL_intlk		: 7,
	      UNUSED_IN_9	: 8,
	      Start		: 9,
	      Stop		: 10,
	      SaveParms		: 11,
	      RestoreParms	: 12,
	      DefaultParms	: 13,
	      SyncTime		: 14,
	      ResetVibeSw	: 15,
	      ResetSoftStart	: 16,
	      ClearFaults	: 17,
	      HiAvePwrFault	: 18,
	      InterlockFault	: 19,
	      ManualCtrl	: 20,
	      GustFault		: 21,
	      SoftStartFault	: 22,
	      HiAveRpmFault	: 23,
	      StopGenerator	: 24,
	      ResetManualCtrl	: 25,
	      EnableRS232	: 26,
	      DisableRS232	: 27,
	      Reset		: 28,
	      ZeroEnergy	: 29,
	      ResetWindMinMax	: 30,
	      ClearError	: 31,
              StartGenerator    : 32
}

PLC.Output = { motor_ramp_1	: 0,
	       motor_ramp_2	: 1,
	       softstart_reset	: 2,
	       WDT_trigger	: 3,
	       vibe_sw_reset	: 4,
	       brake_release	: 5,
	       line_connect	: 6,
	       spare_out_8	: 7,
	       UNUSED_OUT_9	: 8,
	       wind_gt_cutin_lo : 9,
	       wind_lt_cutin_hi : 10,
	       wind_lt_cutout_lo: 11,
	       wind_gt_cutout_hi: 12,
	       interlock_OK	: 13,
	       enable_rpm_ave	: 14,
	       bad_gen_state	: 15,
	       wattnode_failure : 16,
	       manual_control	: 17,
	       stop_generator	: 18,
	       wattnode_polling : 19,
	       gust_fault	: 20,
	       gen_temp_trig	: 21,
	       gen_temp_fault	: 22,
	       softstart_fault	: 23,
	       overspeed_fault	: 24,
	       brake_stop_fault : 25,
	       brake_sync_fault : 26,
	       enable_brake_tmr : 27,
	       bad_shutdn_state : 28,
	       rpm_nonzero	: 29,
	       ss_output	: 30,
	       comp_on_state	: 31,
	       last_comp_on	: 32,
	       compressor_on	: 33,
	       hi_ave_pwr_fault : 34,
	       interlock_fault	: 35,
	       intlk_flt_delay	: 36,
	       hi_ave_rpm_fault : 37,
	       lc_ready		: 38,
	       shutdown_active	: 39,
	       rs232_polling	: 40,
	       wattnode_setup	: 41,
	       anemometer	: 42,
	       runtime_error	: 43,
	       pause_on_error	: 44
}

PLC.DataW = { PLC_DATA_MEMORY_1		: 1,
	      PLC_DATA_MEMORY_2		: 2,
	      dummy			: 3,
	      msg_in			: 4,
	      msg_out			: 5,
	      PowerIntScale		: 6,
	      wattnode_reg_value	: 7,
	      wattnode_err_status	: 8,
	      wind_filter_index		: 9,
	      wind_filter_samples	: 10,
	      Wind_cut_in_low		: 11,
	      Wind_cut_out_low		: 12,
	      Wind_cut_out_high		: 13,
	      Wind_cut_in_high		: 14,
	      wind_filter_len		: 15,
	      index			: 16,
	      gen_filter_index		: 17,
	      gen_filter_samples	: 18,
	      Gen_RPM_Avg		: 19,
	      RPM_Avg_samples		: 20,
	      RPM_cut_in		: 21,
	      RPM_cut_out		: 22,
	      RPM_timeout_sec		: 23,
	      RPM_cutout_timer		: 24,
	      powerup_delay_sec		: 25,
	      powerup_delay_timer	: 26,
	      gen_rpm_state		: 27,
	      air_comp_on_cnt		: 28,
	      hi_ave_pwr_time		: 29,
	      sync_rpm			: 30,
	      brake_stop_time		: 31,
	      brake_sync_time		: 32,
	      man_ctrl_timeout		: 33,
	      max_gust_speed		: 34,
	      max_gusts			: 35,
	      gust_count		: 36,
	      gust_fault_time		: 37,
	      gen_temp_fault_time	: 38,
	      ss_fault_time		: 39,
	      hi_ave_rpm_time		: 40,
	      interlock_fault_time	: 41,
	      brake_timer		: 42,
	      rpm_nonzero_count		: 43,
	      nonzero_rpm_counter	: 44,
	      rpm_os_count		: 45,
	      os_rpm_counter		: 46,
	      soft_os_rpm		: 47,
	      hi_ave_rpm		: 48,
	      lc_lo_rpm			: 49,
	      lc_hi_rpm			: 50,
	      lc_ready_delay		: 51,
	      min_wind_speed		: 52,
	      max_wind_speed		: 53,
	      option_bits		: 54,
	      Meas_Wind_Avg		: 55,
	      Meas_Wind_Inst		: 56,
	      Gen_RPM			: 57,
	      wind_status		: 58,
	      PowerSum_hi		: 59,
	      PowerSum_lo		: 60,
	      EnergyPosSum_hi		: 61,
	      EnergyPosSum_lo		: 62,
	      EnergyNegSum_hi		: 63,
	      EnergyNegSum_lo		: 64,
	      Demand_hi			: 65,
	      Demand_lo			: 66,
	      gen_period_hi		: 67,
	      gen_period_lo		: 68,
	      gen_rpm_x10_hi		: 69,
	      gen_rpm_x10_lo		: 70,
	      wind_period_hi		: 71,
	      wind_period_lo		: 72,
	      wind_speed_x100_hi	: 73,
	      wind_speed_x100_lo	: 74,
	      gen_filter_sum_hi		: 75,
	      gen_filter_sum_lo		: 76,
	      wind_filter_sum_hi	: 77,
	      wind_filter_sum_lo	: 78,
	      PowerFactorAvg_hi		: 79,
	      PowerFactorAvg_lo		: 80,
	      HighAvgPwr_hi		: 81,
	      HighAvgPwr_lo		: 82,
	      SystemUptime_hi		: 83,
	      SystemUptime_lo		: 84,
	      SystemDowntime_hi		: 85,
	      SystemDowntime_lo		: 86
}

PLC.DataL = { PowerSum		: 0,
	      EnergyPosSum	: 1,
	      EnergyNegSum	: 2,
	      Demand		: 3,
	      gen_period	: 4,
	      gen_rpm_x10	: 5,
	      wind_period	: 6,
	      wind_speed_x100	: 7,
	      gen_filter_sum	: 8,
	      wind_filter_sum	: 9,
	      PowerFactorAvg	: 10,
	      HighAvgPwr	: 11,
	      SystemUptime	: 12,
	      SystemDowntime	: 13
}

var CaptionStyle = "font-family: Arial; font-size: 13px;"
var HeadingStyle = "font-size:13px; font-family: Arial; font-weight:bold; font-style:normal"

/****************************************************************
 *      Label(x, y, w, h, bgcolor, fgcolor, sty, txt)           *
 *                                                              *
 *  Draw a label with the specified attributes.                 *
 ****************************************************************/
function Label(x, y, w, h, bgcolor, fgcolor, sty, txt)
{
  this.x = x
  this.y = y
  this.w = w
  this.h = h
  this.bg = bgcolor
  this.fg = fgcolor
  if (sty == null)
    this.sty = CaptionStyle
  else
    this.sty = sty
  this.txt = txt
}	// End function Label

/********************************************************
 *      Led(x, y, siz, colorlo, colorhi, idx)           *
 *                                                      *
 *  Draw a square LED.                                  *
 ********************************************************/
function Led(x, y, siz, colorlo, colorhi, idx)
{
  this.x = x
  this.y = y
  this.siz = siz
  this.lo_color = colorlo
  this.hi_color = colorhi
  this.idx = idx
}	// End function Led

/********************************************************
 *      getStatus()                                     *
 *                                                      *
 *  Check busy status and start fetch cycle if not      *
 *  busy.                                               *
 ********************************************************/
function getStatus() {	
  if (readBusy) return;
  if (clickBusy) return;
  rdy=xmlhttpObj.readyState;
  if(rdy>0 && rdy < 4) return;
  if (LCDr78) getLCD(1);  
  else getLCD(0);
}	// End function getStatus
 
/****************************************
 *      createXMLhttp()                 *
 *                                      *
 ****************************************/
function createXMLhttp() {var xmlhttp;
  /*@cc_on @*/
  /*@if (@_jscript_version >= 5)
     try { xmlhttp=new ActiveXObject("Msxml2.XMLHTTP")}
     catch (e) { try {xmlhttp=new ActiveXObject("Microsoft.XMLHTTP")} 
     catch (E) {xmlhttp=false}
     }
    @else
     xmlhttp=false
    @end @*/

  if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
     try { xmlhttp = new XMLHttpRequest()}
     catch (e) { xmlhttp=false}
  }

  if (!xmlhttp && window.createRequest) {
     try {xmlhttp = window.createRequest()} 
     catch (e) {xmlhttp=false}
  }
  return xmlhttp
}	// End function createXMLhttp

/****************************************
 *      errormsg(txt)                   *
 *                                      *
 *  Pop-up error message.               *
 ****************************************/
function errormsg(txt){alert("Connection Failed:"+txt)}

var errCount=0

/************************************************
 *      checkSvrResp(xmlobject, from)           *
 *                                              *
 *  Check for response from server.             *
 ************************************************/
function checkSvrResp(xmlobject, from){
   if (xmlobject.status!=200 || xmlobject.responseText==""){
     errCount++
	 if (errCount > 3) {svrOK=0;drawMsgColor("Server Error!","red");return 0;}
	 else return 1
   }
   errCount=0
   svrOK=1
   return 1
}	// End function checkSvrResp

/****************************************
 *      Variable Declarations           *
 ****************************************/
var xmlhttpObj=createXMLhttp()
var svrOK=true
var commCount=0
var clickBusy=0;
var readBusy=0;
var RR10Satus = 0 // store relay ch #10 (rly bit 129 to 134)
var LCDr78=1
var lineNo=1

// arrays to store retrieved PLC data
var LCDLine=new Array() // 4 lines starts from LCDLine[1]
var INPUT = new Array() // Digital inputs
var OUTPUT = new Array()// Digital outputs
var DM = new Array()	// 58 DMs from DM[1]
var DM32 = new Array()	// 14 32-bit variables from DM32[30]

var Caption = new Array()
Caption[0] =  new Label(24, 150, 120, 0, '#efffff', '#007fff', null, 'Generating')
Caption[1] =  new Label(24, 170, 120, 0, '#efffff', '#007fff', null, 'Brake Status')
Caption[2] =  new Label(24, 190, 120, 0, '#efffff', '#007fff', null, 'Wind Status')
Caption[3] =  new Label(24, 210, 120, 0, '#efffff', '#007fff', null, 'Inst. Wind Speed')
Caption[4] =  new Label(24, 230, 120, 0, '#efffff', '#007fff', null, 'Ave. Wind Speed')
Caption[5] =  new Label(24, 250, 120, 0, '#efffff', '#007fff', null, 'Inst. Power Output')
Caption[6] =  new Label(24, 270, 120, 0, '#efffff', '#007fff', null, 'Ave. Power Output')

Caption[7] =  new Label(24, 290, 120, 0, '#efffff', '#007fff', null, 'Rotor Speed')
Caption[8] =  new Label(24, 310, 120, 0, '#efffff', '#007fff', null, 'Generator Speed')
Caption[9] =  new Label(24, 330, 120, 0, '#efffff', '#007fff', null, 'Compressor Status')
Caption[10]=  new Label(24, 350, 120, 0, '#efffff', '#007fff', null, 'Energy Generated')
Caption[11]=  new Label(24, 370, 120, 0, '#efffff', '#007fff', null, 'Energy Consumed')

Caption[12] = new Label(24, 405, 0,   0, 'white',   'blue', HeadingStyle, 'INTERLOCK STATUS:')
Caption[13] = new Label(34, 425, 75, 0, '#efffff', '#007fff', null, 'Vib. Switch')
Caption[14] = new Label(34, 445, 75, 0, '#efffff', '#007fff', null, 'Overspeed')
Caption[15] = new Label(34, 465, 75, 0, '#efffff', '#007fff', null, 'Gen. Temp.')
Caption[16] = new Label(34, 485, 75, 0, '#efffff', '#007fff', null, 'Soft Starter')
Caption[17] = new Label(34, 505, 75, 0, '#efffff', '#007fff', null, 'E-Stop')
Caption[18] = new Label(34, 525, 75, 0, '#efffff', '#007fff', null, 'SEL-547')

Caption[19] = new Label(200, 405, 0,   0, 'white',   'blue', HeadingStyle, 'FAULT STATUS:')
Caption[20] = new Label(210, 425, 105, 0, '#efffff', '#007fff', null, 'Gust')
Caption[21] = new Label(210, 445, 105, 0, '#efffff', '#007fff', null, 'Overspeed')
Caption[22] = new Label(210, 465, 105, 0, '#efffff', '#007fff', null, 'Gen. Temp.')
Caption[23] = new Label(210, 485, 105, 0, '#efffff', '#007fff', null, 'Soft Starter')
Caption[24] = new Label(210, 505, 105, 0, '#efffff', '#007fff', null, 'Brake Stop')
Caption[25] = new Label(210, 525, 105, 0, '#efffff', '#007fff', null, 'Brake Sync')
Caption[26] = new Label(210, 545, 105, 0, '#efffff', '#007fff', null, 'High Ave. Power')
Caption[27] = new Label(210, 565, 105, 0, '#efffff', '#007fff', null, 'High Ave. Speed')

var InterlockLED = new Array()
InterlockLED[0] = new Led(110, 425, 14, 'red', 'green', PLC.Input.vibe_intlk)
InterlockLED[1] = new Led(110, 445, 14, 'red', 'green', PLC.Input.overspeed_intlk)
InterlockLED[2] = new Led(110, 465, 14, 'red', 'green', PLC.Input.gen_prot_intlk)
InterlockLED[3] = new Led(110, 485, 14, 'red', 'green', PLC.Input.soft_start_intlk)
InterlockLED[4] = new Led(110, 505, 14, 'red', 'green', PLC.Input.e_stop_intlk)
InterlockLED[5] = new Led(110, 525, 14, 'red', 'green', PLC.Input.SEL_intlk)

var FaultLED = new Array()
FaultLED[0] = new Led(316, 425, 14, 'green', 'red', PLC.Output.gust_fault)
FaultLED[1] = new Led(316, 445, 14, 'green', 'red', PLC.Output.overspeed_fault)
FaultLED[2] = new Led(316, 465, 14, 'green', 'red', PLC.Output.gen_temp_fault)
FaultLED[3] = new Led(316, 485, 14, 'green', 'red', PLC.Output.softstart_fault)
FaultLED[4] = new Led(316, 505, 14, 'green', 'red', PLC.Output.brake_stop_fault)
FaultLED[5] = new Led(316, 525, 14, 'green', 'red', PLC.Output.brake_sync_fault)
FaultLED[6] = new Led(316, 545, 14, 'green', 'red', PLC.Output.hi_ave_pwr_fault)
FaultLED[7] = new Led(316, 565, 14, 'green', 'red', PLC.Output.hi_ave_rpm_fault)

/****************************************
 *      timestamp()                     *
 *                                      *
 *  Get a current timestamp.            *
 ****************************************/
function timestamp(){ return "?%0C"+new Date().getTime()}

/************************************************
 *      getLCDr78()                             *
 *                                              *
 *  Check PLC firmware version, get LCD lines   *
 *  and start data fetch cycle if rev 78.       *
 ************************************************/
function getLCDr78(){  
  readBusy = 1;
  var cmd = "HOSTLINK/RV$0"+timestamp()
  var resp
  xmlhttpObj.open("GET",cmd, true)  
  try {
    xmlhttpObj.onreadystatechange=function() {
      if (xmlhttpObj.readyState==4) {
	if (!checkSvrResp(xmlhttpObj, "getLCDr78")) return  
	if (xmlhttpObj.responseText.substring(0,2)=="ER") { LCDr78=0; getIO()}
	resp = xmlhttpObj.responseText.substring(3);
	for (i=0;i<4;i++) {
	  k = resp.indexOf("&|")
	  LCDLine[i+1]=resp.substring(0,k)
	  resp = resp.substring(k+2)
	}		    
	if (clickBusy) return			
	getIO();
      }
    }
    xmlhttpObj.send(null)
  } catch (e){errormsg("Failed at getLCD"+e)}
}	// End function getLCDr78

/************************************************
 *      getLCDOrg(getAll)                       *
 *                                              *
 *  Get LCD lines and start data fetch cycle    *
 *  for older PLC's.                            *
 *  If getAll is true, get all LCD lines.       *
 ************************************************/
function getLCDOrg(getAll){  
  readBusy = 1;
  var cmd = "HOSTLINK/RV$"+(lineNo)+timestamp()
  xmlhttpObj.open("GET",cmd, true)  
  try {
    xmlhttpObj.onreadystatechange=function() {
      if (xmlhttpObj.readyState==4) {
	if (!checkSvrResp(xmlhttpObj, "getLCDOrg")) return  
	  LCDLine[lineNo] = xmlhttpObj.responseText.substring(3)
	if (clickBusy) return
	if (lineNo<4) {
	  lineNo++
	  if (getAll) {getLCD(1);return}
	}
	else lineNo=1	     
	getIO()
      }
    }
    xmlhttpObj.send(null)
  } catch (e){errormsg("Failed at getLCDOrg"+e)}
}	// End function getLCDOrg

/************************************************
 *      getLCD(getAll)                          *
 *                                              *
 *  High-level function to get one or all LCD   *
 *  lines.  getAll = true to get all 4 lines.   *
 ************************************************/
function getLCD(getAll) {   // getAll = true to get all 4 lines
  if (getAll==1 && LCDr78==1) return getLCDr78()
  else return getLCDOrg(getAll)
}

/************************************************
 *      getIO()                                 *
 *                                              *
 *  Get input and output bits from PLC.         *
 ************************************************/
function getIO()
{
  var cmd = "HOSTLINK/RR000000130000" + timestamp() // Read I/O bits that we want
  xmlhttpObj.open("GET", cmd, true)
  try {
    xmlhttpObj.onreadystatechange=function() {
      if (xmlhttpObj.readyState==4) {
	if (!checkSvrResp(xmlhttpObj,"getIO")) return
	  dataStr = xmlhttpObj.responseText.substring(4)
	bits = parseInt(dataStr.substring(0, 4), 16)	// Get 16 input bits
	mask = 1
	for (i = 0; i < 8; i++) {	// We only care about the first 8 inputs
	  if (bits & mask)
	    INPUT[i] = 1
	  else
	    INPUT[i] = 0
	  mask <<= 1
	}
	var bits = parseInt(dataStr.substring(64, 68), 16)	// Get first 16 outputs
	var mask = 1
	for (i = 0; i < 16; i++) {
	  if (bits & mask)
	    OUTPUT[i] = 1
	  else
	    OUTPUT[i] = 0
	  mask <<= 1
	}
	bits = parseInt(dataStr.substring(68, 72), 16)	// Get next 16 bits
	mask = 1    // Reset mask
	for (i = 16; i < 32; i++) {
	  if (bits & mask)
	    OUTPUT[i] = 1
	  else
	    OUTPUT[i] = 0
	  mask <<= 1
	}
	bits = parseInt(dataStr.substring(72, 76), 16)	// Get last 16 bits
	mask = 1    // Reset mask
	for (i = 32; i < 48; i++) {
	  if (bits & mask)
	    OUTPUT[i] = 1
	  else
	    OUTPUT[i] = 0
	  mask <<= 1
	}
	if (clickBusy) return
	else getDM()
      }
    }
    xmlhttpObj.send(null)
  } catch (e){errormsg("Failed at getIO"+e)}
}   // End function getIO

/************************************************
 *      getDM()                                 *
 *                                              *
 *  Get DM variables from PLC.                  *
 ************************************************/
function getDM()
{
  var cmd = "HOSTLINK/RD0055000C"+timestamp() // Read 12 words
  xmlhttpObj.open("GET",cmd, true)
  try {
    xmlhttpObj.onreadystatechange=function() {
      if (xmlhttpObj.readyState==4) {
	if (!checkSvrResp(xmlhttpObj,"getDM")) return
	dataStr = xmlhttpObj.responseText.substring(4)
	for (i = 0; i < 4; i++) {	// The first four are 16-bit data
	  DM[i+55] = h2d(dataStr.substring(i*4, i*4+4))
	}
	var idx = 0;
	for (i = 4; i < 12; i += 2) {	// The rest are 32-bit data
	  DM32[idx] = (parseInt(dataStr.substring(i*4, i*4+4), 16) << 16);	// Get high word of value
	  DM32[idx++] += parseInt(dataStr.substring(i*4+4, i*4+8), 16);		// Add low word of value
	}
	refreshScreen()
	commCount++
	drawMsg("Cycle=" + commCount)
	svrOK = 1
	readBusy = 0
      }
    }
    xmlhttpObj.send(null)
  } catch (e){errormsg("Failed at getDM"+e)}
}	// End function getDM


var intervalID

/************************************************
 *      doUpdate()                              *
 *                                              *
 *  Update the page with optional scheduling.   *
 ************************************************/
function doUpdate() {
  if (document.form1.C1.checked) intervalID=setInterval("getStatus()", 1100)	// Update every 1.1s
  else clearInterval(intervalID)
}

/************************************************
 *      doClick(el)                             *
 *                                              *
 *  OnClick handler for specified element.      *
 ************************************************/
function doClick(el) 
{
  var xmlhttpClick= createXMLhttp()
  var r = true;
  clickBusy =1
  clearInterval(intervalID);
  if ((el.id).substring(0,3)=="Btn") {	 
    var bit_addr = parseInt((el.id).substring(3), 10)
    var val = 0xff
    if (bit_addr == PLC.Input.StopGenerator) {	        // If stop/start button
      if (el.style.backgroundColor == 'green') {	// Trying to start
        bit_addr = PLC.Input.StartGenerator
	r = confirm("Start the turbine?")
      }
      else
	r = confirm("Stop the turbine?")
    }
    if (r) {
      cmd="HOSTLINK/Wb"+d2h4(bit_addr)+d2h(val)+timestamp()
      clearInterval(intervalID);
      xmlhttpClick.open("GET",cmd,true);
      try {
	xmlhttpClick.onreadystatechange=function() {
	  if (xmlhttpClick.readyState==4) {
	    checkSvrResp(xmlhttpClick,"doClick")
	    clickBusy=0	    
	    doUpdate()
	  }
	}
	xmlhttpClick.send(null)
      } catch (e){errormsg("Failed at doClick"+e)}
    }
    else {
      clickBusy=0	    
      doUpdate()
    }
  }
}	// End function doClick

/****************************************
 *      d2h(d)                          *
 *                                      *
 *  Convert decimal to hex string.      *
 ****************************************/
function d2h(d) {
  if (d < 0) d = 0xFFFFFFFF + d + 1;
  var res = d.toString(16).toUpperCase()
  var l = res.length;

  if (l > 2) res = res.substring(l-2,l)
  else if (l==1) {
     res = "0"+res
  }
  return res
}

/****************************************
 *      d2h4(d)                         *
 *                                      *
 *  Convert decimal to four-character   *
 *  hex string.                         *
 ****************************************/
function d2h4(d) {
  if (d < 0) d = 0xFFFFFFFF + d + 1;
  var res = d.toString(16).toUpperCase()
  var l = res.length;

  if (l > 4) res = res.substring(l-4,l)
  else if (l==1) res = "0"+res
  while (res.length<4) res = "0"+res
  return res
}	// End function d2h4

/****************************************
 *      h2d(h)                          *
 *                                      *
 *  Convert hex string to decimal.      *
 ****************************************/
function h2d(h) {
  var val16 = parseInt(h,16)
  if (val16 > 32767) val16 += -65536
  return val16
}	// End function h2d

/********************************************************************************
 *      drawRect(el, x, y, w, h, bg, bCol, txt, tcolor, styleStr, clickable)    *
 *                                                                              *
 *  Draw a rectangle with the specified attributes.                             *
 ********************************************************************************/
function drawRect(el, x, y, w, h, bg, bCol, txt, tcolor, styleStr, clickable){
 var style = el.style
 if (w) style.width = w +"px"
 if (h) style.height = h +"px"
 style.left = x +"px"
 style.top = y +"px"
 style.color=tcolor
 style.backgroundColor = bg
 style.borderColor = bCol
 style.position = "absolute"
 style.borderStyle = "solid"
 style.borderWidth = 1
 if (clickable) {
   style.textAlign = "center"
   el.innerHTML = "&nbsp;<a href=javascript:doClick(" + el.id +
		  ") style=font-size:18px;color:" + tcolor +
		  ">" + txt + "</a>&nbsp;</font>"
 }
 else
   el.innerHTML = "<div style='"+styleStr+"'>&nbsp;" + txt + "&nbsp;</div>"
}	// End function drawRect

/********************************************************
 *      drawObj(nam, id,                                *
 *              x, y,                                   *
 *		w, h,                                   *
 *		bg, bCol, txt, tcolor,                  *
 *              styleStr, clickable)                    *
 *                                                      *
 *  High-level draw function.                           *
 ********************************************************/
function drawObj(nam, id,
		 x, y, 
		 w, h, 
		 bg, bCol, txt, tcolor,
		 styleStr, clickable)
{
  var el = document.getElementById(nam + (id))
  if (el==null) {
    el = document.createElement("div")
    el.id = nam+(id)
    document.body.appendChild(el)
  }
  drawRect(el,
	   x, y,
	   w, h,
	   bg,
	   bCol,
	   txt,
	   tcolor,
	   styleStr,
	   clickable)
}	// End function drawObj
 
/************************************************
 *      drawMsgColor(txt,col)                   *
 *                                              *
 *  Draw color text message at fixed location.  *
 ************************************************/
function drawMsgColor(txt,col){
  x=105;y=33;w=140;h=16
  var el = document.getElementById("msg")
  if (el==null) {
     el = document.createElement("div")
     el.id = "msg"
     document.body.appendChild(el)
  }
  drawRect(el,x,y,w,h,"white","black",txt,col,msgStyle,0)
}	// End function drawMsgColor

/************************************************
 *      drawMsg(txt)                            *
 *                                              *
 *  High-level text message draw function.      *
 ************************************************/
function drawMsg(txt){
   drawMsgColor(txt,"blue")
}	// End function drawMsg

/****************************************
 *      drawLCD()                       *
 *                                      *
 *  Draw all LCD lines.                 *
 ****************************************/
function drawLCD() {
  for (i=0;i<4;i++) drawLCDLine(i);
}	// End function drawLCD

/****************************************
 *      drawLCDLine(line)               *
 *                                      *
 *  Draw a single LCD line.             *
 ****************************************/
function drawLCDLine(line){
  var bgcol = LCDbgColor
  w=LCDSIZE[0]
  h=LCDSIZE[1]
  x=LCDXY[0]
  y=LCDXY[1]

  var elf = document.getElementById("lcdframe")
  if (elf==null) {
     elf = document.createElement("div")
     elf.id = "lcdframe"
     document.body.appendChild(elf)
     drawRect(elf,x-1,y-1,w+2,h*4+2,bgcol,"black","","","",0)
  }   
  var el = document.getElementById("LCD"+(line+1))
  if (el==null) {
     el = document.createElement("div")
     el.id = "LCD"+(line)
     document.body.appendChild(el)
  }
  drawRect(el,x,y+line*LCDSIZE[1],w,h,bgcol,bgcol,LCDLine[line+1],LCDTextColor,LCDTextStyle,0)
}	// End function drawLCDLine

/************************************************
 *      drawCaptions()                          *
 *                                              *
 *  Draw all text captions at once.             *
 ************************************************/
function drawCaptions()
{
  for (i = 0; i < Caption.length; i++) {
    drawObj("Label", i,
	    Caption[i].x, Caption[i].y,
	    Caption[i].w, Caption[i].h,
	    Caption[i].bg,
	    Caption[i].bg,
	    Caption[i].txt,
	    Caption[i].fg,
	    Caption[i].sty,
	    0)
  }
}	// End function drawCaptions

function drawButtons()
{
  var label = "Stop"
  var bgcolor = 'red'
  
  if (OUTPUT[PLC.Output.stop_generator]) {
    label = "Start"
    bgcolor = 'green'
  }
  drawObj("Btn", PLC.Input.StopGenerator,
	  40, 600,
	  80, 0,
	  bgcolor,
	  'black',
	  label,
	  'white',
	  null,
	  1)
  drawObj("Btn", PLC.Input.ClearFaults,
	  200, 600,
	  130, 0,
	  'yellow',
	  'black',
	  "Reset Faults",
	  'red',
	  null,
	  1)
}	// End function drawButtons

function drawLED(nam, id, x, y, siz, color)
{
  drawObj(nam, id,
	  x, y,
	  siz, siz,
	  color,
	  'black',
	  "",
	  color,
	  null,
	  0)
}	// End function drawLED

/************************************************
 *      drawStatusLeds()                        *
 *                                              *
 *  Draw all LED's.                             *
 ************************************************/
function drawStatusLeds()
{
  var colr
  
	// Draw interlock LED's
  for (i = 0; i < InterlockLED.length; i++) {
    if (INPUT[InterlockLED[i].idx])
      colr = InterlockLED[i].hi_color
    else
      colr = InterlockLED[i].lo_color
    drawLED("Intlk", i,
	    InterlockLED[i].x, InterlockLED[i].y,
	    InterlockLED[i].siz,
	    colr)
  }
	// Draw fault LED's
  for (i = 0; i < FaultLED.length; i++) {
    if (OUTPUT[FaultLED[i].idx])
      colr = FaultLED[i].hi_color
    else
      colr = FaultLED[i].lo_color
    drawLED("Fault", i,
	    FaultLED[i].x, FaultLED[i].y,
	    FaultLED[i].siz,
	    colr)
  }
}	// End function drawStatusLeds

/****************************************
 *      MPH(cms)                        *
 *                                      *
 *  Convert cm/s to mph.                *
 ****************************************/
function MPH(cms)
{
  return(cms * 36.0 / 1609.34)
}	// End function MPH

/****************************************
 *      drawDataParms()                 *
 *                                      *
 *  Draw all parameter values.          *
 ****************************************/
function drawDataParms()
{
  var msg
  var num, val1, val2
  
  if (OUTPUT[PLC.Output.ss_output])
    msg = "YES"
  else
    msg = "NO"
  drawObj("Parm", 0,
	  144, 150,
	  80, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)
  
  if (OUTPUT[PLC.Output.brake_release])
    msg = "RELEASED"
  else
    msg = "SET"
  drawObj("Parm", 1,
	  144, 170,
	  80, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)
  
  switch(DM[PLC.DataW.wind_status]) {
    case 0:
      msg = "LOW"
      break;
    
    case 1:
      msg = "OK"
      break;
    
    case 2:
      msg = "HIGH"
      break;
  }
  drawObj("Parm", 2,
	  144, 190,
	  80, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)
  
  num = DM[PLC.DataW.Meas_Wind_Inst] / 100.0
  val1 = num.toFixed(2)
  num = MPH(DM[PLC.DataW.Meas_Wind_Inst])
  val2 = num.toFixed(2)
  msg = val1.toString() + " m/s (" + val2.toString() + " mph)"	
  drawObj("Parm", 3,
	  144, 210,
	  0, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)
	    
  num = DM[PLC.DataW.Meas_Wind_Avg] / 100.0
  val1 = num.toFixed(2)
  num = MPH(DM[PLC.DataW.Meas_Wind_Avg])
  val2 = num.toFixed(2)
  msg = val1.toString() + " m/s (" + val2.toString() + " mph)"	
  drawObj("Parm", 4,
	  144, 230,
	  0, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)
	    
  num = DM32[PLC.DataL.PowerSum] / 1000.0
  val1 = num.toFixed(0)
  msg = val1.toString() + " kW";
  drawObj("Parm", 5,
	  144, 250,
	  0, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)

  num = DM32[PLC.DataL.Demand] / 1000.0
  val1 = num.toFixed(0)
  msg = val1.toString() + " kW";
  drawObj("Parm", 6,
	  144, 270,
	  0, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)

  num = DM[PLC.DataW.Gen_RPM] / 10.0
  val1 = num.toFixed(1)
  num = DM[PLC.DataW.Gen_RPM] / 201.09
  val2 = num.toFixed(1)
  msg = val2.toString() + " rpm"
  drawObj("Parm", 7,
	  144, 290,
	  0, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)

  msg = val1.toString() + " rpm"
  drawObj("Parm", 8,
	  144, 310,
	  0, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)

  if (OUTPUT[PLC.Output.compressor_on])
    msg = "ON"
  else
    msg = "OFF"
  drawObj("Parm", 10,
	  144, 330,
	  80, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)
  
  num = DM32[PLC.DataL.EnergyPosSum] / 10.0
  val1 = num.toFixed(0)
  msg = val1.toString() + " kWh";
  drawObj("Parm", 11,
	  144, 350,
	  0, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)

  num = DM32[PLC.DataL.EnergyNegSum] / 10.0
  val1 = num.toFixed(0)
  msg = val1.toString() + " kWh";
  drawObj("Parm", 12,
	  144, 370,
	  0, 0,
	  'white', 'white',
	  msg,
	  'black',
	  HeadingStyle,
	  0)
}	// End function drawDataParms
